package indi.fritz.bigmap.volme.offset;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import indi.fritz.bigmap.common.DBException;
import indi.fritz.bigmap.util.ByteUtil;
import indi.fritz.bigmap.volme.model.DataOffset;

public class OffsetEngine implements AutoCloseable{
	File file;
	RandomAccessFile reader;
	
	public OffsetEngine(File file) throws IOException {
		this.file = file;
		if(!this.file.exists()) {
			throw new DBException.NotOffsetFile();
		}
		this.reader = new RandomAccessFile(file, "rw");
	}
	
	public DataOffset readOffset(long index) throws IOException {
		if(index < 0){ return null; }
		synchronized (this.reader) {
			try(OffseetEngineLock lock = getLock(DataOffset.SIZE * index, DataOffset.SIZE, true)){
				long count = this.reader.length();
				if(count % DataOffset.SIZE != 0){ throw new DBException("this offset file has damaged"); }
				if(count / DataOffset.SIZE < index){ return null; }
				this.reader.seek(index * DataOffset.SIZE);
				byte[] b = new byte[DataOffset.SIZE];
				this.reader.read(b, 0, b.length);
				return DataOffset.format(b);
			}
		}
	}
	
	public long updateOffset(long index, DataOffset offset) throws IOException {
		synchronized (this.reader) {
			try(OffseetEngineLock lock = getLock(DataOffset.SIZE * index, DataOffset.SIZE, false)){
				long count = this.reader.length();
				if(count % DataOffset.SIZE != 0 || count / DataOffset.SIZE < index){ throw new DBException("this offset file has damaged"); }
				this.reader.seek(index * DataOffset.SIZE);
				this.reader.write(offset.getByte());
			}
		}
		return index;
	}
	
	public DataOffset deleteOffset(long index) throws IOException {
		if(index < 0){ return null; }
		synchronized (this.reader) {
			try(OffseetEngineLock lock = getLock(DataOffset.SIZE * index, DataOffset.SIZE, false)){
				long count = this.reader.length();
				if(count % DataOffset.SIZE != 0 || count / DataOffset.SIZE < index){ throw new DBException("this offset file has damaged"); }
				this.reader.seek(index * DataOffset.SIZE);
				byte[] b = new byte[DataOffset.SIZE];
				this.reader.read(b, 0, b.length);
				DataOffset offset = DataOffset.format(b);
				this.reader.seek((index * DataOffset.SIZE) + DataOffset.EXIST_INDEX);
				this.reader.write(ByteUtil.booleanToByte(false));
				offset.setExist(false);
				return offset;
			}
		}
	}
	
	public long writeOffset(DataOffset offset) throws IOException {
		long index = 0;
		synchronized (this.reader) {
			long start = this.reader.length();
			try(OffseetEngineLock lock = getLock(start, DataOffset.SIZE, false)){
				if(start % DataOffset.SIZE != 0){ throw new DBException("this offset file has damaged"); }
				this.reader.seek(start);
				this.reader.write(offset.getByte());
				index = start / DataOffset.SIZE;	
			}
		}
		return index;
	}
	
	public int count() throws IOException{
		synchronized (this.reader) {
			try(OffseetEngineLock lock = getLock(0L, Integer.MAX_VALUE, false)){
				long count = this.reader.length();
				if(count % DataOffset.SIZE != 0){ throw new DBException("this offset file has damaged"); }
				return (int)(count / DataOffset.SIZE);
			}
		}
	}
	
	public void clear() throws IOException {
		synchronized (this.reader) {
			try(OffseetEngineLock lock = getLock(0L, Long.MAX_VALUE, false)){
				this.reader.setLength(0);
			}
		}
	}
	
	private OffseetEngineLock getLock(long position,long size,boolean shared) throws IOException {
		return new OffseetEngineLock(this.reader, position, size, shared);
	}

	@Override
	public void close() throws IOException {
		if(this.reader != null) {
			this.reader.close();
		}
		this.reader = null;
	}


}
